package com.hspedu.seckill.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.hspedu.seckill.entity.User;
import com.hspedu.seckill.model.annotation.AccessLimit;
import com.hspedu.seckill.model.constant.RespBeanEnum;
import com.hspedu.seckill.model.context.UserContext;
import com.hspedu.seckill.model.result.RespBean;
import com.hspedu.seckill.service.UserService;
import com.hspedu.seckill.utils.CookieUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author: xujiabing
 * @date: 2025-02-16 17:14
 * @description: 接口限流+获取登录用户-拦截器
 */
@Component
public class AccessLimitInterceptor implements HandlerInterceptor {

    @Resource
    private UserService userService;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * @description: 1.获取user对象放入到 userHolder 2.接口限流防刷
     * @author: xujiabing
     * @date: 2025-02-16 17:20
     * @param: request
     * @param: response
     * @param: handler
     * @return: boolean
     */
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        /*HandlerMethod 是Spring MVC中封装了控制器方法信息的类。
        当请求映射到某个Controller的特定方法时，
        Spring会将该方法（及其所属的Controller、方法参数、注解等元数据）包装成一个 HandlerMethod 对象。
        用途：通过 HandlerMethod 可以获取方法级别的详细信息*/
        if (handler instanceof HandlerMethod) {
            // step-1 获取user信息，封装到 ThreadLocal
            User user = getUser(request, response);
            // 存入 ThreadLocal 中
            UserContext.setUser(user);

            //step-2 处理接口限流防刷机制
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);

            if (Objects.isNull(accessLimit)) {
                // 不需要限流的接口,直接放行
                return true;
            }
            // 获取接口上注解配置
            int second = accessLimit.second();
            int maxCount = accessLimit.maxCount();
            boolean needLogin = accessLimit.needLogin();

            // 需要登录的接口,校验用户是否登录
            if (needLogin && Objects.isNull(user)) {
                render(response, RespBeanEnum.SESSION_ERROR);
                return false;
            }else if (!needLogin) {
                // 不需要登录的接口,就不进行限流防刷
                return true;
            }
            // 下面是接口限流逻辑
            String uri = request.getRequestURI();
            String limitKey = uri + ":" + user.getId();
            ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
            // 获取当前访问次数
            Integer count = (Integer) valueOperations.get(limitKey);

            if (Objects.isNull(count)) { // 第一次访问
                redisTemplate.opsForValue().set(limitKey, 1, second, TimeUnit.SECONDS);
            } else if (count < maxCount) { // 访问次数小于 5 次
                redisTemplate.opsForValue().increment(limitKey);
            } else { // 访问次数大于 5 次
                render(response, RespBeanEnum.ACCESS_LIMIT_REACHED);
                return false;
            }

        }
        return true;
    }

    // 构建返回对象
    private void render(HttpServletResponse response, RespBeanEnum sessionError) throws IOException {
        System.out.println("render-" + sessionError.getMessage());
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        PrintWriter out = response.getWriter();
        RespBean error = RespBean.error(sessionError);
        out.write(new ObjectMapper().writeValueAsString(error));
        out.flush();
        out.close();
    }

    /**
     * 获取当前登录用户
     * @param request
     * @param response
     * @return
     */
    private User getUser(HttpServletRequest request,
                         HttpServletResponse response) {
        String ticket = CookieUtil.getCookieValue(request, "userTicket");
        if (StringUtils.isEmpty(ticket)) {
            return null;
        }
        return userService.getUserByCookie(ticket, request, response);
    }
}
